package query.insert

import ast.expr.SqlIdentifierExpr
import ast.statement.insert.SqlInsert
import dsl.column
import expr.DB
import expr.Query
import query.ReviseQuery
import query.select.Select
import query.select.SelectQuery
import util.toSqlString
import visitor.getExpr
import visitor.getQueryExpr
import java.sql.Connection

class NativeInsert(
    var db: DB = DB.MYSQL,
    override var conn: Connection? = null,
    override var isTransaction: Boolean = false
) : ReviseQuery() {
    constructor(db: DB) : this(db, null, false)

    private var sqlInsert = SqlInsert()

    private var columns = mutableListOf<String>()

    private var records = mutableListOf<MutableMap<String, Any?>>()

    infix fun into(table: String): NativeInsert {
        sqlInsert.table = SqlIdentifierExpr(table)
        return this
    }

    infix fun columns(columns: List<String>): NativeInsert {
        this.columns = columns.toMutableList()
        this.sqlInsert.columns.addAll(columns.map { SqlIdentifierExpr(it) })
        return this
    }

    fun columns(vararg column: String): NativeInsert {
        this.columns = column.toMutableList()
        this.sqlInsert.columns.addAll(columns.map { SqlIdentifierExpr(it) })
        return this
    }

    infix fun select(select: Select.() -> SelectQuery): NativeInsert {
        this.sqlInsert.query = select(Select(db)).getSelect()
        return this
    }

    infix fun value(value: MutableMap<String, Any?>): NativeInsert {
        if (columns.isEmpty()) {
            value.forEach {
                sqlInsert.columns.add(SqlIdentifierExpr(it.key))
                columns.add(it.key)
            }
        }

        sqlInsert.values.add(columns.map {
            when (val cell = value[it]) {
                is Query -> getQueryExpr(cell, this.db).expr
                else -> getExpr(cell)
            }
        })

        records.add(value)

        return this
    }

    infix fun values(values: List<MutableMap<String, Any?>>): NativeInsert {
        values.forEach {
            value(it)
        }

        return this
    }

    fun values(vararg values: MutableMap<String, Any?>): NativeInsert {
        return values(values.toList())
    }

    override fun sql() = toSqlString(sqlInsert, db)

    override fun exec(): Int {
        val result = database.execReturnKey(conn!!, this.sql())
        if (!isTransaction) {
            conn!!.close()
        }

        if (records.isNotEmpty()) {
            result.forEachIndexed { index, item ->
                records[index]["incrKey"] = item
            }
        }
        return records.size
    }

    fun execReturnKey(): List<Long> {
        val result = database.execReturnKey(conn!!, this.sql())
        if (!isTransaction) {
            conn!!.close()
        }

        return result
    }
}